import { useCallback, useEffect, useRef, useState, forwardRef, useImperativeHandle, Ref } from 'react'
import type {IOnChange, Schema, ToRenderProps} from './types'
import { ProItem } from './components'
import css from './index.less'
import {ValueContext} from './components/context'
import {uuid} from '@inbiz/utils'
function JsonEditorCore ({value, editor}: ToRenderProps, ref: Ref<{getValue: () => Promise<Schema>}>) {
  // 用于保存真正提交的数据
  const valueRef = useRef<typeof value>();
  const errorRef = useRef<Record<string, boolean | undefined>>({})  
  const [newData, $newData] = useState<Schema | undefined>(value)

  useEffect(() => {
    valueRef.current = value ? JSON.parse(JSON.stringify(value)) : {};
    errorRef.current = {};
    $newData(value)
  }, [value]);


  const handleChange: IOnChange = useCallback(({value, path, key, type, id, error}) => {
    if (id) {
      errorRef.current[id] = !!error
    }
    if (type == 'destroy') {
      return
    }
    const uid = uuid();
    const updatefun = (keys: string[],data :any) => {
      if (keys.length === 0) {
        if (key === 'type') {
          data.type = value;
          delete data.items;
          delete data.properties
          if (value === 'object') {
            data.properties = {
              [`__inbiz_${uid}`]: {type: 'string'}
            }
            errorRef.current[`__inbiz_${uid}`] = true
          }
          if (value === 'array') {
            data.items = {type:'string'}
          }
        } else if (type === 'add') {
          data.properties[`__inbiz_${uid}`] = {type: 'string'}
          Object.setPrototypeOf(data.properties[`__inbiz_${uid}`], {_id: `__inbiz_${uid}`});
        }
      }

      keys.reduce((pre:any, next:string, index: number, arr: string[]) => {
        const isLast =  index === arr.length - 1;
        let item, data:{key:string}| undefined, dataKey = '';
        if (next === ':array') {
          pre.items = {...pre.items};
          item = pre.items
        } else {
          if (pre.properties) {
            pre.properties = {...pre.properties };
            Object.entries(pre.properties).forEach(([key, item]) => {
              //@ts-ignore
              if (item?._id && item?._id === (isLast ? id : next)) {
                data = item as {key:string};
                dataKey = key
              }
            })
            if (data) {
              delete pre.properties[data?.key];
              if (next.indexOf('__inbiz_') === 0 && dataKey) {
                pre.properties[dataKey] = {...data};
                if (data?._id) {
                  Object.setPrototypeOf(pre.properties[dataKey], {_id: data?._id, key: data?.key});
                }
              } else {
                pre.properties[next] = {...data};
                if (data?._id) {
                  Object.setPrototypeOf(pre.properties[next], {_id: data?._id, key: data?.key});
                }
              }
            } else {
              pre.properties[next] = {...pre.properties[next]}
            }
          }
          item =  pre.properties ? pre.properties[dataKey || next] : pre
        }
        if (isLast) {
          if (type === 'update') {
            if (key === 'keyName') {
              const data = pre.properties[dataKey || next];
              Object.setPrototypeOf(data, {_id: id, key: value});
              delete pre.properties[next]
              if (dataKey) {
                delete pre.properties[dataKey]
              }
              pre.properties[value!] = data;
              return data
            }
            if (key === 'type') {
              if (next === ':array') {
                pre.items.type = value;
                delete pre.properties;
                pre.items.properties = {};
                if (value === 'object') {
                  pre.items.properties = {
                    [`__inbiz_${uid}`]: {type: 'string'}
                  }
                  errorRef.current[`__inbiz_${uid}`] = true
                }
                if (value === 'array') {
                  pre.items.items = {type:'string'}
                }
              } else {
                pre.properties[dataKey || next].type = value;
                if (next.indexOf('__inbiz_') === 0 && dataKey) {
                  Object.setPrototypeOf(pre.properties[dataKey || next], {_id: next, key: dataKey});
                }
                
                if (value === 'object') {
                  pre.properties[dataKey || next].properties = {
                    [`__inbiz_${uid}`]: {type: 'string'}
                  }
                  errorRef.current[`__inbiz_${uid}`] = true
                }
                if (value === 'array') {
                  pre.properties[dataKey || next].items = {type:'string'}
                }
              }
            }
          } else if (type === 'add') {
            if (next === ':array') {
              pre.items.properties = {...(pre.items.properties || {})}
              pre.items.properties[`__inbiz_${uid}`] = {type: 'string'}
            } else {
              pre.properties[next].properties = {...(pre.properties[next]?.properties) || {}};
              pre.properties[next].properties[`__inbiz_${uid}`] = {type: 'string'}
              Object.setPrototypeOf(pre.properties[next].properties[`__inbiz_${uid}`], {_id: `__inbiz_${uid}`});
            }
          } else if (type === 'delete') {
            delete pre.properties[next]
            Object.keys(pre.properties).forEach(item => {
              if (pre.properties?.[item]?._id === next) {
                delete pre.properties[item]
              }
            })
          }
        }
        return item;
      }, data);
    }
    $newData(oldValue => {
      const keys = path.split('/').slice(1);
      let data: Schema | undefined = undefined;
      const isUpdate = (type === 'update' && key == 'type') || type != 'update';
      if (isUpdate) {
        data = {...oldValue} as Schema
        updatefun(keys, data);
        valueRef.current = data;
      } else {
        updatefun(keys, valueRef.current);
      }
      console.log(isUpdate ? data : oldValue)
      return isUpdate ? data : oldValue as Schema
    })
  }, []);

  useImperativeHandle(ref, () => ({
    getValue: () => {
      if (Object.values(errorRef.current).some(item => item)) {
        return Promise.reject('校验失败')
      } else {
        return Promise.resolve(valueRef.current as Schema)
      }
    }
  }), []);
  return value ? (
    <div className={css.schema}>
      {['unknown', 'follow'].includes(value.type) ? (
        <div className={css.invalid}>（无效的类型）</div>
      ) : (
        <ValueContext.Provider value={valueRef}>
          <ProItem val={newData!} xpath={''} root={true} editor={editor} onChange={handleChange} />
        </ValueContext.Provider>
      )}
    </div>
  ) : null
}

export default forwardRef(JsonEditorCore)

